package com.my;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/***
 * 四则运算题目生成
 */
public class HelloMath {


    public static void main(String[] args) {
        //生成文件位置和文件名
        String targetFile = "D:/hello-math.txt";

        //生成问题数量
        int questionNumber = 50;

        //连算个数（几个数进行四则运算），比如： 1+1  则为2个连算个数
        int numberItems = 2;

        //单个数最大值
        int everyMax = 20;

        //结果值最大值
        int resultMax = 100;

        //计算符号
        Map<String, String> calculatorMap = new HashMap<>();
        calculatorMap.put("+", "+");
        calculatorMap.put("-", "-");
        //        calculatorMap.put("*", "x");
        //        calculatorMap.put("/", "÷");

        //计算过程是否允许出现负数
        boolean canBeNegative = false;

        createQuestions(questionNumber, numberItems, everyMax, resultMax, calculatorMap, canBeNegative, true, PaperSizeEnum.RE_MIN_110);
    }

    private static void createQuestions(int questionNumber, int numberItems, int everyMax, int resultMax, Map<String, String> calculatorMap,
                                        boolean canBeNegative, boolean isShowAnswer, PaperSizeEnum paperSizeEnum) {
        List<Question> questions = IntStream.range(0, questionNumber).mapToObj(i -> {
            return createQuestion(numberItems, everyMax, resultMax, calculatorMap, canBeNegative);
        }).collect(Collectors.toList());


        //输出文本
        List<String> questionStrs = wrapperQuestion(questions, calculatorMap, false);
        //对齐
        StringBuilder showSb = getShowStringFromQuestion(questionStrs, paperSizeEnum);

        System.out.println("=======================");
        System.out.println(showSb);
        System.out.println("=======================");


        if (isShowAnswer) {
            //同时输出答案
            List<String> questionStrs2 = wrapperQuestion(questions, calculatorMap, true);
            //对齐
            StringBuilder showSb2 = getShowStringFromQuestion(questionStrs2, paperSizeEnum);

            System.out.println("↓↓↓↓↓↓↓↓↓↓答案↓↓↓↓↓↓↓↓↓↓");
            System.out.println(showSb2);
            System.out.println("=======================");

        }

    }

    private static StringBuilder getShowStringFromQuestion(List<String> questionStrs, PaperSizeEnum paperSizeEnum) {
        StringBuilder showSb = new StringBuilder();
        for (int i = 0; i < questionStrs.size(); i++) {
            String str = questionStrs.get(i);

            //            everyLineShow
            if (str.length() < paperSizeEnum.everyQuestionAlignmentSpaceNum) {
                //补充空格对齐
                String spaceStr = IntStream.range(0, paperSizeEnum.everyQuestionAlignmentSpaceNum - str.length()).mapToObj(value -> " ").collect(Collectors.joining());
                showSb.append(str).append(spaceStr);
            } else {
                showSb.append(str);
            }

            if ((i + 1) % paperSizeEnum.everyLineShow == 0) {
                showSb.append("\n");
            }
        }
        return showSb;
    }

    private static List<String> wrapperQuestion(List<Question> questions, Map<String, String> calculatorMap, boolean isShowAnswer) {

        return questions.stream().map(question -> {
            Integer[] numbers = question.getNumbers();
            String[] operators = question.getOperators();

            String concatStr = IntStream.range(0, numbers.length).mapToObj(j -> {
                return (calculatorMap.getOrDefault(operators[j], "") + " " + numbers[j]).trim();
            }).collect(Collectors.joining(" "));

            if (isShowAnswer) {
                concatStr = concatStr + Optional.ofNullable(operators[numbers.length]).orElse("") + " = " + question.getAnswer();
            } else {
                concatStr = concatStr + Optional.ofNullable(operators[numbers.length]).orElse("") + " =        ";
            }
            return concatStr;

        }).collect(Collectors.toList());
    }


    public static class Question {
        /**存储数字*/
        Integer[] numbers;
        /**存储运算符*/
        String[] operators;
        /** 答案 */
        String answer;

        public Integer[] getNumbers() {
            return numbers;
        }

        public void setNumbers(Integer[] numbers) {
            this.numbers = numbers;
        }

        public String[] getOperators() {
            return operators;
        }

        public void setOperators(String[] operators) {
            this.operators = operators;
        }

        public String getAnswer() {
            return answer;
        }

        public void setAnswer(String answer) {
            this.answer = answer;
        }
    }

    public static Question createQuestion(Integer numberItems, Integer everyMax, Integer resultMax, Map<String, String> calculatorMap, boolean canBeNegative) {
        String[] calculators = calculatorMap.keySet().toArray(new String[0]);

        //存储数字
        Integer[] numbers = new Integer[numberItems];
        //存储运算符
        String[] operators = new String[numberItems + 1];
        String answer = "";

        for (int i = 0; i < numberItems; i++) {

            if (i == 0) {
                //初始第一个数字
                numbers[0] = ThreadLocalRandom.current().nextInt(0, everyMax);
            } else {
                //非第一个数
                while (true) {
                    //随机获取运算符
                    String calculator = calculators[ThreadLocalRandom.current().nextInt(0, calculators.length)];
                    int everyNum = ThreadLocalRandom.current().nextInt(0, everyMax);
                    if (everyNum == 0 && calculator.equals("/")) {
                        //除数不能为0
                        continue;
                    }
                    double computeReult = compute(numbers[i - 1], everyNum, calculator);
                    if (!canBeNegative && computeReult < 0) {
                        //出现负数，则重新获取
                        continue;
                    }
                    if (computeReult > resultMax) {
                        //超出结果值最大值，重新获取
                        continue;
                    }
                    operators[i] = calculator;
                    numbers[i] = everyNum;
                    answer = BigDecimal.valueOf(computeReult).stripTrailingZeros().toPlainString();
                    break;
                }
            }

        }
        Question question = new Question();
        question.setNumbers(numbers);
        question.setOperators(operators);
        question.setAnswer(answer);

        return question;
    }

    public static double compute(double a, double b, String calculator) {
        BigDecimal result;
        switch (calculator) {
            case "+":
                result = BigDecimal.valueOf(a).add(BigDecimal.valueOf(b));
                break;
            case "-":
                result = BigDecimal.valueOf(a).subtract(BigDecimal.valueOf(b));
                break;
            case "*":
                result = BigDecimal.valueOf(a).multiply(BigDecimal.valueOf(b));
                break;
            case "/":
                if (b == 0) {
                    throw new IllegalArgumentException("除数不能为0,需要另外处理");
                }
                result = BigDecimal.valueOf(a).divide(BigDecimal.valueOf(b), 2, RoundingMode.HALF_UP);
                break;
            default:
                throw new IllegalArgumentException("Unexpected value: " + calculator);
        }
        //        return result.stripTrailingZeros().toPlainString();  去除小数点后的0
        return result.doubleValue();
    }


    public enum PaperSizeEnum {

        /** 热敏纸 110mm宽度 */
        RE_MIN_110(3, 17),

        /** A4纸 210mm宽度 大约能够存储40行，共计160个题目 */
        A4_220(4, 22),

        ;

        //每行显示几个题目
        private final Integer everyLineShow;
        //每个问题的长度,对齐每列,用于排版对齐(计算方式:每行总容纳字数/每行显示问题数)
        private final Integer everyQuestionAlignmentSpaceNum;

        PaperSizeEnum(Integer everyLineShow, Integer everyQuestionAlignmentSpaceNum) {
            this.everyLineShow = everyLineShow;
            this.everyQuestionAlignmentSpaceNum = everyQuestionAlignmentSpaceNum;
        }

        public Integer getEveryLineShow() {
            return everyLineShow;
        }

        public Integer getEveryQuestionAlignmentSpaceNum() {
            return everyQuestionAlignmentSpaceNum;
        }
    }

}
